///String类型
/// Rust 里面有两种字符串类型。String 和 str。
// &str
// str 类型基本上不怎么使用，通常使用 &str 类型，它其实是 [u8] 类型的切片形式 &[u8]。这是一种固定大小的字符串类型。
// 常见的的字符串字面值就是 &'static str 类型。这是一种带有 'static 生命周期的 &str 类型。
/*String*/
//String 是一个带有的 vec:Vec<u8> 成员的结构体，你可以理解为 str 类型的动态形式。 它们的关系相当于 [T] 和 Vec<T> 的关系。
// 显然 String 类型也有压入和弹出。
#[test]
pub fn string_demo(){
    //从 &str 类型转化成 String类型
    let mut s = String::from("Hello"); //从堆上分配内存保存 "Hello"字符串， 变量s 拥有这块内存， s变量在栈上保存堆内存的地址和字符串的长度以及堆内存的大小。
    // 压入字符和压入字符串切片
    s.push('w');
    s.push_str(" World");
    println!("{}", s);
    s = String::from("World");
    let s2 = s; // s在栈上内容会拷贝给s2，s2也指向堆上的"Hello", s自动失效，以后不能再用,避免s,s2都释放空间造成2次释放。
    println!("{}", s2);
}//到这里，已经超出了s2变量的作用域，rust会自动调用drop函数，释放堆上分配的内存


/*clone*/
#[test]
pub fn clone_demo(){

    let s1 = String::from("Hello");
    let s2 = s1.clone(); //克隆，s1栈上的内容和堆上的内容都会复制一份
    println!("{}", s1);
    println!("{}", s2);
}
/*字符串作为参数*/
#[test]
pub fn string_param_demo(){
    let s = String::from("Hello");
    print_string(s); //s作为了参数,失效了，后面不能再用
}
pub fn print_string(s: String){
    println!("{}", s);
} //s超出作用域，会释放对应的堆内存


/*字符串作为返回值*/
pub fn string_return_demo(){
    let s1 = get_string(); //s1变量拥有返回的字符串对应堆空间的所有权
    println!("{}", s1);
    let s2 = String::from("World");
    let s3 = get_string_from_give(s2); //s3变量拥有返回的字符串对应堆空间的所有权,s2失效
    println!("{}", s3);
}
pub fn get_string()->String{
    let s = String::from("Hello");
    s
}
pub fn get_string_from_give(s: String)->String{
    s
}

/*返还所有权*/
pub fn return_ownership(){
    let s1 = String::from("hello");
    let (s2, len) = calculate_len(s1); //s1失效，s2拥有返回返回字符串的所有权
    println!("{}, {}", s2, len);
}

//计算字符长度，同时返回该字符串的所有权和长度
fn calculate_len(s: String)->(String, usize){
    let len = s.len();
    (s, len)
}
/*引用*/
pub fn ref_demo(){
    let s1 = String::from("hello");
    //引用作为参数，所有权不发生转移
    let len = get_string_len(&s1);
    println!("{},{}", s1, len);

    let mut s2 = String::from("abc");
    //引用作为参数，所有权不发生转移
    let len = get_string_len2(&mut s2);
    println!("{},{}", s2, len);

    let s3 = &mut s2;
    //let s4 = &mut s2; //编译错误，同一个作用域只能有一个变量保存可变引用，防止数据竞争

    let s4 = &s1; //不可变变量引用则可以赋值给多个变量
    let s5 = &s1;
    println!("{}, {}, {}", s3, s4, s5);
}
//引用作为参数
pub fn get_string_len(s: &String) -> usize {
    //s.push_str("ok"); //s是不可变的，不能修改
    s.len()
}
//引用作为参数
fn get_string_len2(s: &mut String) -> usize {
    s.push_str("ok"); //s是可变变量，能修改
    s.len()
}

//返回第一个单词，返回切片
fn first_word(s: &String) -> &str{
    let bytes = s.as_bytes();
    //iter 方法返回集合中的每一个元素
    // enumerate 包装了 iter 的结果，将这些元素作为元组的一部分来返回。enumerate 返回的元组中，第一个元素是索引，第二个元素是集合中元素的引用。这比我们自己计算索引要方便一些
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' '{ //找到空格
            return &s[..i];
        }
    }
    &s[..]
}

/*字符串切片 &str*/
pub fn slice_demo(){
    let s: &str = "I am ok"; //字面量字符串的类型就是字符串切片
    let s = String::from("Hello World");
    let hello = &s[0..5]; //包含头不包含尾 变量中会保存字符串的起始位置和长度
    let hello = &s[..5]; //包含头不包含尾
    let world = &s[6..11];
    let world = &s[6..];

    println!("{}, {}", hello, world);

    let s = first_word(&s);
    println!("{}", s);
}

/*字符串切片作为参数*/
pub fn slice_param(){
    let s = String::from("Hello world");
    let s = first_word_2(&s[..]);
    println!("{}", s);
}

fn first_word_2(s: &str)->&str{
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' '{ //找到空格
            return &s[..i];
        }
    }
    &s[..]
}

//字符串
#[test]
pub fn string_demo2(){
    let data: &str = "Hello World";
    let mut s: String = data.to_string();
    println!("{}", s);

    //更新String
    s.push_str(" I am ok ");
    println!("{}", s);

    s.push('A');
    println!("{}", s);

    //拼接
    let s2 = String::from(" 好的");
    let s3 = s + &s2; //s所有权已经转移 不能再访问
    println!("{}", s3);

    let ss1 = "hello".to_string();
    let ss2 = "world".to_string();
    let ss3 = "ok".to_string();
    //使用format!宏拼接,不会发生所有权转移
    let ss4 = format!("{}-{}-{}", ss1, ss2, ss3);
    println!("{}", ss4);

    //还可以继续使用
    println!("{}", ss1);
    println!("{}", ss2);
    println!("{}", ss3);


    //len返回的是所占字节数
    let len = String::from("Hello 你好").len();
    println!("{}", len); //12

    let name = String::from("English 中文");
    //按字节遍历
    println!("按字节遍历：");
    for b in name.bytes(){
        println!("{}", b);
    }
    //按unicode标量遍历
    println!("按unicode标量遍历:");
    for c in name.chars(){
        println!("{}", c);
    }

    //截取字符串
    let hello = String::from("Hello World");
    let s: &str = &hello[5..];
    println!("{}", s);

    //字符串分割
    let split = hello.split(" ");

    for s in split {
        println!("{}", s);
    }

    //删除
    let mut s = String::from("My name is Perter!中故宫");
    s.remove(0);
    println!("{}", s); //y name is Perter!中故宫

    //从末尾删除
    s.pop();
    println!("{}", s); //y name is Perter!中故

    //删除参数对应下标之后的字符
    s.truncate(4);
    println!("{}", s); //y na

    //清空
    s.clear();

    //删除固定范围内的字符串
    let mut s1 = String::from("lisi");
    s1.drain(0..2);
    println!("{}", s1); //si
}

//替换，查找，批量修改等
#[test]
pub fn string_demo3(){
    let mut s = String::from("道可道，非常道。名可名，非恒名!");
    println!("{}", s.starts_with("道")); //true
    println!("{}", s.ends_with("名")); //true

    //转数字
    let num: i32 = "-23".parse().unwrap();
    println!("{}", num); //-23

    //重复n次，创建新的字符串
    let s2 = s.repeat(2);
    println!("{}", s2); //道可道，非常道。名可名，非恒名!道可道，非常道。名可名，非恒名!

    //替换
    s = s.replace("道", "名");
    println!("{}", s); //名可名，非常名。名可名，非恒名!

    //替换几个
    s = s.replacen("名", "道", 2);
    println!("{}", s); //道可道，非常名。名可名，非恒名!

    let  s = String::from(" 天之道，损有余而补不足。 ");
    //去掉前后空格，返回切片,对s的不可变借用
    let slice  = s.trim();
    //s.clear(); 下面一行打印会用的slice（slice借用了s,所以这里不能clear）
    println!("{}", slice); //天之道，损有余而补不足。

    let index = s.find("道").unwrap();
    println!("{}", index); //7 字节位置
}
#[test]
pub fn format_demo(){
    //type这个地方为空的话默认调用的是Display这个trait。
    //>是一个语义，它表示的是生成的字符串向右对齐，于是我们得到了 0081这个值。与之相对的还有<(向左对齐)和^(居中)。
    //再接下来0是一种特殊的填充语法，他表示用0补齐数字的空位，要注意的是，当0作用于负数的时候，
    // 比如例子中wayslog的体重是-81，那么你最终将得到-0081;当然了，什么都不写表示用空格填充;在这一位上，还会出现+、#的语法，使用比较诡异，一般情况下用不上。
    //最后是一个组合式子width$，这里呢，大家很快就能认出来是表示后面key-value值对中的width=4。你们没猜错，这个值表示格式化完成后字符串的长度。
    // 它可以是一个精确的长度数值，也可以是一个以$为结尾的字符串，$前面的部分可以写一个key或者一个postion。
    let s = format!("{1}是个有着{0:>0width$}KG重，{height:?}cm高的大胖子", 81, "wayslog", width=4, height=178);
    // 我被逼的牺牲了自己了……
    print!("{}", s);
}